import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { CreateRoleDto } from './dto/create-role.dto';
import { UpdateRoleDto } from './dto/update-role.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Role } from './entities/role.entity';
import { In, Repository } from 'typeorm';
import { MenuService } from '../menu/menu.service';
import { PageQuery } from '../../../util/entity/page.entity';
import { buildQuery } from '../../../util/common-fn';
import { RoleEnum } from '../../../util/enum/role.enum';
import { UserService } from '../user/user.service';

@Injectable()
export class RoleService {
  constructor(
    @InjectRepository(Role)
    private roleRepository: Repository<Role>,
    @Inject(forwardRef(() => UserService))
    private readonly userService: UserService,
    private readonly menuService: MenuService,
  ) {}

  /**
   * 创建一个角色(name和code都不能重复)
   * @param createRoleDto
   */
  async create(createRoleDto: CreateRoleDto) {
    const tempRole = await this.roleRepository.findOneBy({
      code: createRoleDto.code,
      name: createRoleDto.name,
    });
    if (tempRole) {
      throw new Error('该角色已存在');
    }
    await this.roleRepository.save(createRoleDto);
    return true;
  }

  /**
   * 查询角色,不带分页(用于用户选择角色下拉框)
   */
  findAllNoPage() {
    return this.roleRepository.find();
  }

  /**
   * 获取角色通过id
   * @param id
   */
  getOneByOrFail(id: number) {
    return this.roleRepository.findOneByOrFail({ id });
  }

  /**
   * 修改角色
   * @param id
   * @param updateRoleDto
   */
  update(id: number, updateRoleDto: UpdateRoleDto) {
    return this.roleRepository.update({ id }, updateRoleDto);
  }

  /**
   * 删除角色
   * @param id
   */
  async remove(id: number) {
    await this.roleRepository.findOneByOrFail({ id });
    return this.roleRepository.softDelete({ id });
  }

  findByIds(ids: number[]) {
    return this.roleRepository.find({
      where: {
        id: In(ids),
      },
    });
  }

  /**
   * 通过id找到以及对应的menus
   * @param id
   */
  async getOneWithMenus(id: number) {
    return await this.roleRepository.findOneOrFail({
      where: {
        id,
      },
      relations: {
        menus: true,
      },
    });
  }

  /**
   * 获取角色列表,带分页查询信息
   * @param pageQuery
   */
  findAll(pageQuery: PageQuery<Role>) {
    return this.roleRepository.findAndCount(buildQuery(pageQuery));
  }

  /**
   * 分配权限时候,通过角色id获取角色拥有的菜单ids,但是要去除有父节点的
   * @param id
   */
  async getMenuIdsForSelect(id: number) {
    // 首先通过id找到该角色的所有menus
    const role = await this.getOneWithMenus(id);
    const menuIds = role.menus.map((menu) => menu.id);
    // 每个menu都包含parent
    const menus = await this.menuService.findManyWithChildren(menuIds);
    return menus
      .filter(
        (item) => Array.isArray(item.children) && item.children.length === 0,
      )
      .map((item) => item.id);
  }

  /**
   * 设置/提交 角色选择的菜单
   * @param id
   * @param menuIds
   */
  async setRoleSelectedMenus(id: number, menuIds: number[]) {
    const role = await this.roleRepository.findOneByOrFail({ id });
    role.menus = await this.menuService.findByIds(menuIds);
    await this.roleRepository.save(role);
    return true;
  }

  /**
   * 通过userId找到roleIs
   * @param userId
   */
  getByUserId(userId: number) {
    return this.roleRepository.findBy({
      users: {
        id: userId,
      },
    });
  }

  /**
   * 通过roleIds判断其中是否有root角色
   * @param ids
   */
  async hasRootByIds(ids: number[]) {
    const roles = await this.findByIds(ids);
    return roles
      .map((role) => role.code)
      .some((code) => code === RoleEnum.Root);
  }

  /**
   * id是否拥有某个role
   */
  async hasOneOfRoles(id: number, roles: string[]) {
    return !!(await this.roleRepository.findOne({
      where: {
        id,
        status: 1,
        code: In(roles),
      },
    }));
  }
  /**
   * 不要碰root角色,即使是root角色也不能操作
   * 但root用户能修改自己
   * 返回自己是否为root
   * @param beOperatedId 被操作的id
   * @param operatorUserId
   * @private
   */
  async dontTouchRoot(beOperatedId: number, operatorUserId: number) {
    const [isBeOperatedRoot, isOperatorRoot] = await Promise.all([
      this.hasOneOfRoles(beOperatedId, [RoleEnum.Root]),
      this.userService.hasOneOfRoles(operatorUserId, [RoleEnum.Root]),
    ]);
    // 如果要操作root,但他本身不是root,不被允许
    if (isBeOperatedRoot && !isOperatorRoot) {
      throw new Error(`不能对${RoleEnum.Root}进行任何操作`);
    }
  }
}
